﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Drawing.Design;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.Design;
using System.Web.UI.WebControls;
using log4net;
using MyLib.IO;
using MyLib.Web.Properties;

namespace MyLib.Web.UI {
	/// <summary>
	/// 
	/// </summary>
	[DefaultProperty("FileType")]
	[Designer(typeof(MyPlaceholderDesigner))]
	[ToolboxData("<{0}:MyManageFiles runat=\"server\"></{0}:MyManageFiles>")]
	public class MyManageFiles : DataBoundControl, INamingContainer, ICascadedControl {
		private ILog m_logger = LogManager.GetLogger(typeof(MyManageFiles));

		private const string DefaultErrorTooFewFilesMessage = "Too few files, expected no less than {0}, but was {1}.";

		private const string DefaultErrorTooManyFilesMessage = "Too many files, expected no more than {0}, but was {1}.";

		private IList<FileObject> m_files;

		private IList<HttpPostedFile> m_uploadedFiles;

		private IList<FileObject> m_checkedFiles;

		#region ICascadedControl

		/// <summary>
		/// 
		/// </summary>
		/// <param name="oldValues"></param>
		/// <param name="newValues"></param>
		void ICascadedControl.Saving(IOrderedDictionary oldValues, IOrderedDictionary newValues) {
			ExecuteSaving(oldValues, newValues);
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="oldValues"></param>
		/// <param name="newValues"></param>
		int ICascadedControl.Save(IOrderedDictionary oldValues, IOrderedDictionary newValues) {
			return ExecuteSave(oldValues, newValues);
		}

		/// <summary>
		/// 
		/// </summary>
		CascadedAction ICascadedControl.CascadedAction {
			get {
				return CascadedAction;
			}
			set {
				CascadedAction = value;
			}
		}

		#endregion

		/// <summary>
		/// 
		/// </summary>
		private void PrepareSave() {
			if (m_uploadedFiles == null)
				m_uploadedFiles = GetUploadedFiles();

			if (m_checkedFiles == null)
				m_checkedFiles = GetCheckedFiles();
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="oldValues"></param>
		/// <param name="newValues"></param>
		/// <returns></returns>
		protected virtual void ExecuteSaving(IOrderedDictionary oldValues, IOrderedDictionary newValues) {
			PrepareSave();

			CheckFileCount(m_files.Except(m_checkedFiles), m_uploadedFiles);
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="oldValues"></param>
		/// <param name="newValues"></param>
		/// <returns></returns>
		protected virtual int ExecuteSave(IOrderedDictionary oldValues, IOrderedDictionary newValues) {
			PrepareSave();

			var data = GetData();
			if (data == null)
				return 0;

			return ExecuteSave(data, m_uploadedFiles, m_checkedFiles);
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="data"></param>
		/// <returns></returns>
		private IList<FileObject> GetExistingFiles(DataSourceView data) {
			var existingFiles = new List<FileObject>();

			data.Select(CreateDataSourceSelectArguments(), (d) => {
				existingFiles.AddRange(d.OfType<FileObject>());
			});

			return existingFiles;
		}

		/// <summary>
		/// 
		/// </summary>
		/// <returns></returns>
		private IList<HttpPostedFile> GetUploadedFiles() {
			var uploadedFiles = new List<HttpPostedFile>();

			foreach (var key in Page.Request.Files.AllKeys) {
				if (!key.StartsWith(UniqueID)) {
					// Skip this file.
					continue;
				}

				var postedFile = Page.Request.Files[key];

				if (postedFile == null)
					continue;

				if (postedFile.ContentLength == 0)
					continue;

				uploadedFiles.Add(postedFile);
			}
			return uploadedFiles;
		}

		/// <summary>
		/// 
		/// </summary>
		/// <returns></returns>
		private IList<FileObject> GetCheckedFiles() {
			var checkedFiles = Page.Request.Form[UniqueID + "_CheckFile"];

			if (!string.IsNullOrEmpty(checkedFiles)) {
				var idArray = checkedFiles.Split(',');
				return m_files
					.Where(_ => idArray.Contains(_.Id))
					.ToArray();
			} else {
				return new FileObject[0];
			}
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="existingFiles"></param>
		/// <param name="uploadedFiles"></param>
		private void CheckFileCount(IEnumerable<FileObject> existingFiles, IEnumerable<HttpPostedFile> uploadedFiles) {
			var estimatedFileCount = uploadedFiles.Count() + existingFiles.Count();

			if (estimatedFileCount > MaxFileCount) {
				throw new ApplicationException(string.Format(ErrorTooManyFilesMessage, MaxFileCount, estimatedFileCount));
			}

			if (estimatedFileCount < MinFileCount) {
				throw new ApplicationException(string.Format(ErrorTooFewFilesMessage, MinFileCount, estimatedFileCount));
			}
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="data"></param>
		/// <param name="uploadedFiles"></param>
		/// <param name="checkedFiles"></param>
		/// <returns></returns>
		private int ExecuteSave(DataSourceView data, IEnumerable<HttpPostedFile> uploadedFiles, IEnumerable<FileObject> checkedFiles) {
			// Save all uploaded files.

			return ExecuteSaveInsert(data, uploadedFiles)

			// Delete all checked files.

			+ ExecuteSaveDelete(data, checkedFiles);
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="value"></param>
		/// <returns></returns>
		private static bool IsNullValue(object value) {
			if (value == null) {
				return true;
			}

			return value.ToString().Length == 0;
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="data"></param>
		/// <param name="uploadedFiles"></param>
		/// <returns></returns>
		private int ExecuteSaveInsert(DataSourceView data, IEnumerable<HttpPostedFile> uploadedFiles) {
			var c = 0;

			var saveValues = new OrderedDictionary(StringComparer.OrdinalIgnoreCase);
			foreach (var postedFile in uploadedFiles) {
				byte[] content;
				using (var ms = new System.IO.MemoryStream(postedFile.ContentLength)) {
					postedFile.InputStream.CopyTo(ms);

					content = ms.ToArray();

					ms.Close();
				}

				saveValues["fileName"] = System.IO.Path.GetFileName(postedFile.FileName);
				saveValues["content"] = content;
				saveValues["description"] = string.Empty;
				saveValues["summary"] = string.Empty;

				data.Insert(saveValues, (rc, ex) => {
					c += rc;

					if (ex != null)
						m_logger.Debug("Failed to insert file.", ex);

					return true;
				});
			}

			return c;
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="data"></param>
		/// <param name="checkedFiles"></param>
		/// <returns></returns>
		private int ExecuteSaveDelete(DataSourceView data, IEnumerable<FileObject> checkedFiles) {
			var c = 0;

			var deleteKeys = new OrderedDictionary(StringComparer.OrdinalIgnoreCase);

			foreach (var checkFile in checkedFiles) {
				deleteKeys["fileId"] = checkFile.Id;
				data.Delete(deleteKeys, null, (rc, ex) => {
					c += rc;

					if (ex != null)
						m_logger.Debug("Failed to delete file.", ex);

					return true;
				});
			}

			return c;
		}

		/// <summary>
		/// 
		/// </summary>
		[Bindable(true)]
		[Category("Appearance")]
		[DefaultValue("~/File.axd")]
		public string DownloadUrl { get; set; }

		/// <summary>
		/// 
		/// </summary>
		[Category("Appearance")]
		[Editor(typeof(System.Web.UI.Design.ImageUrlEditor), typeof(UITypeEditor))]
		[UrlProperty]
		public string DeleteImageUrl { get; set; }

		/// <summary>
		/// 
		/// </summary>
		[Category("Appearance")]
		[Editor(typeof(System.Web.UI.Design.ImageUrlEditor), typeof(UITypeEditor))]
		[UrlProperty]
		public string RecoverImageUrl { get; set; }

		/// <summary>
		/// 
		/// </summary>
		[Bindable(false)]
		[Category("Action")]
		[Description("MyManageFiles_CascadedAction")]
		[DefaultValue(CascadedAction.Save)]
		public CascadedAction CascadedAction {
			get;
			set;
		}

		/// <summary>
		/// 
		/// </summary>
		[Bindable(false)]
		[Category("Action")]
		[Description("MyManageFiles_MaxFiles")]
		[DefaultValue(int.MaxValue)]
		public int MaxFileCount {
			get;
			set;
		}

		/// <summary>
		/// 
		/// </summary>
		[Bindable(false)]
		[Category("Action")]
		[Description("MyManageFiles_MinFiles")]
		[DefaultValue(0)]
		public int MinFileCount {
			get;
			set;
		}

		/// <summary>
		/// 
		/// </summary>
		[Bindable(false)]
		[Category("Appearance")]
		[DefaultValue(DefaultErrorTooFewFilesMessage)]
		public string ErrorTooFewFilesMessage { get; set; }

		/// <summary>
		/// 
		/// </summary>
		[Bindable(false)]
		[Category("Appearance")]
		[DefaultValue(DefaultErrorTooManyFilesMessage)]
		public string ErrorTooManyFilesMessage { get; set; }

		/// <summary>
		/// 
		/// </summary>
		protected override HtmlTextWriterTag TagKey {
			get {
				return HtmlTextWriterTag.Ul;
			}
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="e"></param>
		protected override void OnInit(System.EventArgs e) {
			base.OnInit(e);

			if (Page != null) {
				Page.RegisterRequiresControlState(this);
			}
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="e"></param>
		protected override void OnLoad(EventArgs e) {
			if (this.Page.Form != null) {
				this.Page.Form.Enctype = "multipart/form-data";
			}
		}

		/// <summary>
		/// 
		/// </summary>
		protected override void PerformSelect() {
			// Call OnDataBinding here if bound to a data source using the
			// DataSource property (instead of a DataSourceID), because the
			// databinding statement is evaluated before the call to GetData.       
			if (!IsBoundUsingDataSourceID) {
				OnDataBinding(EventArgs.Empty);
			}

			var data = GetData();
			if (data != null) {
				// The GetData method retrieves the DataSourceView object from  
				// the IDataSource associated with the data-bound control.
				data.Select(CreateDataSourceSelectArguments(), OnDataSourceViewSelectCallback);

				// The PerformDataBinding method has completed.
				RequiresDataBinding = false;
				MarkAsDataBound();

				// Raise the DataBound event.
				OnDataBound(EventArgs.Empty);
			}
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="retrievedData"></param>
		private void OnDataSourceViewSelectCallback(IEnumerable retrievedData) {

			// Call OnDataBinding only if it has not already been 
			// called in the PerformSelect method.
			if (IsBoundUsingDataSourceID) {
				OnDataBinding(EventArgs.Empty);
			}
			// The PerformDataBinding method binds the data in the  
			// retrievedData collection to elements of the data-bound control.
			PerformDataBinding(retrievedData);
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="retrievedData"></param>
		protected override void PerformDataBinding(IEnumerable retrievedData) {
			base.PerformDataBinding(retrievedData);

			// If the data is retrieved from an IDataSource as an 
			// IEnumerable collection, attempt to save its values to file list.
			if (retrievedData != null) {

				foreach (object dataItem in retrievedData) {
					var ofile = dataItem as FileObject;

					if (ofile == null)
						continue;

					m_files.Add(ofile);
				}
			}
		}

		/// <summary>
		/// 
		/// </summary>
		protected override void CreateChildControls() {
			base.CreateChildControls();

			Controls.Clear();

			if (DesignMode) {
				var dd = new Literal();
				dd.Text = "[" + ID + "]";
				Controls.Add(dd);
				return;
			}

			var durl = ResolveUrl(DownloadUrl.Trim());

			var seq = 0;
			foreach (var ofile in m_files) {
				// Skip special file(s).
				if (ofile.Name.StartsWith("__"))
					continue;

				var item = new WebControl(HtmlTextWriterTag.Li);
				item.ID = "Item" + seq;
				item.Attributes["seq"] = Convert.ToString(seq);

				var cbx = new MyInputControl("checkbox");
				cbx.ID = "CheckFile" + seq;
				cbx.Value = ofile.Id;
				cbx.Name = UniqueID + "_CheckFile";
				cbx.Attributes["title"] = Main.FileCheckedFileWillBeDeleted;
				cbx.Style[HtmlTextWriterStyle.Display] = "none"; // Hide checkbox.

				var lnk = new HyperLink();
				lnk.ID = "DownloadFile" + seq;
				lnk.NavigateUrl = DownloadUrl + "?file_id=" + ofile.Id;
				lnk.ToolTip = string.Format(Main.FileClickToDownload, ofile.Name);
				lnk.Text = ofile.Name.AbbreviatedTo();

				var ibt1 = new ImageButton();
				ibt1.ID = "DeleteFile" + seq;
				ibt1.CssClass = "deleteFile";
				ibt1.ImageUrl = DeleteImageUrl;
				ibt1.ImageAlign = ImageAlign.AbsMiddle;
				ibt1.AlternateText = Main.FileRemove;
				ibt1.CausesValidation = false;
				ibt1.ToolTip = Main.FileRemove + " [" + ofile.Name + "]";
				ibt1.OnClientClick = string.Format("return myManageFiles_checkFile('{0}', {1});", this.ClientID, seq);

				var ibt2 = new ImageButton();
				ibt2.ID = "RecoverFile" + seq;
				ibt2.CssClass = "recoverFile";
				ibt2.ImageUrl = RecoverImageUrl;
				ibt2.ImageAlign = ImageAlign.AbsMiddle;
				ibt2.AlternateText = Main.FileRecover;
				ibt2.CausesValidation = false;
				ibt2.ToolTip = Main.FileRecover + " [" + ofile.Name + "]";
				ibt2.Style[HtmlTextWriterStyle.Display] = "none"; // Hide button2.
				ibt2.OnClientClick = string.Format("return myManageFiles_uncheckFile('{0}', {1});", this.ClientID, seq);

				item.Controls.Add(cbx);
				item.Controls.Add(lnk);
				item.Controls.Add(ibt1);
				item.Controls.Add(ibt2);

				Controls.Add(item);

				++seq;
			} // end of foreach.

			CreateAdditionChildControls(seq);

			ChildControlsCreated = true;
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="seq"></param>
		protected virtual void CreateAdditionChildControls(int seq) {
			// 

			var fitem = new WebControl(HtmlTextWriterTag.Li);
			fitem.ID = "NItem";
			fitem.Attributes["seq"] = Convert.ToString(seq);

			var fcomment = new Literal();
			fcomment.ID = "Comment";
			fcomment.Text = Main.FileNew;
			fcomment.Mode = LiteralMode.Encode;

			var uf = new FileUpload();
			uf.ID = string.Format("File_{0}", seq);
			uf.Attributes["seq"] = Convert.ToString(seq);

			// var dp = new TextBox();
			// dp.ID = string.Format("Description_{0}", seq);
			// uf.Attributes["seq"] = Convert.ToString(seq);

			var ibt1 = new ImageButton();
			ibt1.ID = "DeleteNewFile" + seq;
			ibt1.CssClass = "deleteFile";
			ibt1.ImageUrl = DeleteImageUrl;
			ibt1.ImageAlign = ImageAlign.AbsMiddle;
			ibt1.AlternateText = Main.FileRemove;
			ibt1.CausesValidation = false;
			ibt1.ToolTip = Main.FileRemove;
			ibt1.OnClientClick = string.Format("return myManageFiles_deleteNewFile('{0}', {1});", this.ClientID, seq);

			fitem.Controls.Add(fcomment);
			fitem.Controls.Add(uf);
			// fitem.Controls.Add(dp);
			fitem.Controls.Add(ibt1);
			Controls.Add(fitem);

			// 

			var nfitem = new WebControl(HtmlTextWriterTag.Li);
			nfitem.ID = "None";
			nfitem.CssClass = "none";

			var nf = new HyperLink();
			nf.NavigateUrl = "javascript:void(0);";
			nf.Attributes["onclick"] = string.Format("return myManageFiles_addFile('{0}');", this.ClientID);
			nf.Text = Main.FileAddNew;

			nfitem.Controls.Add(nf);
			Controls.Add(nfitem);
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="savedState"></param>
		protected override void LoadViewState(object savedState) {
			var states = savedState as object[];

			base.LoadViewState(states[0]);

			var files = states[1] as FileObject[];
			if (files != null) {
				m_files = new List<FileObject>(files);
			}
		}

		/// <summary>
		/// 
		/// </summary>
		/// <returns></returns>
		protected override object SaveViewState() {
			object baseState = base.SaveViewState();

			FileObject[] files = null;
			if (m_files != null && m_files.Count != 0)
				files = m_files.ToArray();

			return new object[] { baseState, files };
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="savedState"></param>
		protected override void LoadControlState(object savedState) {
			object[] stateLastRequest = (object[])savedState;

			// Grab the state for the base class
			// and give it to it.
			object baseState = stateLastRequest[0];
			base.LoadControlState(baseState);

			// Now load this control’s state.
			this.DownloadUrl = (string)stateLastRequest[1];
		}

		/// <summary>
		/// 
		/// </summary>
		/// <returns></returns>
		protected override object SaveControlState() {
			// Grab the state for the base control.
			object baseState = base.SaveControlState();

			// Create an array to hold the base control’s state
			// and this control’s state.
			object thisState = new object[] { baseState, DownloadUrl };
			return thisState;
		}

		/// <summary>
		/// 
		/// </summary>
		public MyManageFiles() {
			m_files = new List<FileObject>();

			DownloadUrl = "~/File.axd";
			DeleteImageUrl = "~/Common_Images/deleteFile.png";
			RecoverImageUrl = "~/Common_Images/recoverFile.png";
			CascadedAction = CascadedAction.Save;

			MaxFileCount = int.MaxValue;
			MinFileCount = 0;

			ErrorTooFewFilesMessage = DefaultErrorTooFewFilesMessage;
			ErrorTooManyFilesMessage = DefaultErrorTooManyFilesMessage;
		}
	} // end of ViewFiles.
}
